/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openFuyao is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

package phases

import (
	"context"
	"fmt"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	agentv1beta1 "gopkg.openfuyao.cn/bkeagent/api/v1beta1"
	confv1beta1 "gopkg.openfuyao.cn/bkecommon/cluster/api/v1beta1"
	bkenode "gopkg.openfuyao.cn/bkecommon/cluster/node"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	v12 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/kubernetes/fake"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/record"
	v1beta12 "sigs.k8s.io/cluster-api/api/v1beta1"

	"gopkg.openfuyao.cn/cluster-api-provider-bke/api/v1beta1"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/command"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/phaseframe"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/testutils"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/constant"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/label"
)

var initCxt = context.Background()
var initNewBkeCluster v1beta1.BKECluster
var initOldBkeCluster v1beta1.BKECluster
var initCluster v1beta12.Cluster

var initPhaseContext *phaseframe.PhaseContext
var initLog *v1beta1.BKELogger
var initClient *testutils.BocloudFakeManager
var initScheme *runtime.Scheme
var initRestConfig *rest.Config
var initTServer *httptest.Server

func initNewBkeClusterFun() {
	initNewBkeCluster = v1beta1.BKECluster{
		ObjectMeta: v12.ObjectMeta{
			Name:        "bkecluster",
			Namespace:   "kube-system",
			Annotations: map[string]string{},
			UID:         "newxasdfawefraqwerqwer",
			OwnerReferences: []v12.OwnerReference{
				{Kind: "Cluster", Name: initCluster.Name,
					APIVersion: fmt.Sprintf("%s/%s", v1beta1.GroupVersion.Group, v1beta1.GroupVersion.Version),
				},
			},
		},
		Spec: confv1beta1.BKEClusterSpec{
			ControlPlaneEndpoint: confv1beta1.APIEndpoint{Host: "127.0.0.1", Port: 6443},
			ClusterConfig: &confv1beta1.BKEConfig{
				Cluster: confv1beta1.Cluster{
					ImageRepo:         confv1beta1.Repo{Domain: "deploy.bocloud.k8s", Port: "40443", Prefix: "kubernetes"},
					KubernetesVersion: "v1.29.2",
					OpenFuyaoVersion:  "v2.1.0",
				},
				Nodes: []confv1beta1.Node{
					{Hostname: "cluster", Port: "22", Role: []string{bkenode.MasterWorkerNodeRole, bkenode.EtcdNodeRole,
						bkenode.MasterNodeRole}},
				},
				Addons: nil, CustomExtra: nil,
			},
		},
		Status: confv1beta1.BKEClusterStatus{
			ClusterStatus:     v1beta1.ClusterDeleting,
			PhaseStatus:       confv1beta1.PhaseStatus{{Status: v1beta1.PhaseSucceeded}},
			KubernetesVersion: "v1.24.1",
			NodesStatus: confv1beta1.NodesStates{
				{Node: confv1beta1.Node{Role: []string{bkenode.WorkerNodeRole}, IP: "127.0.0.1"}, StateCode: 1},
			},
		},
	}
}
func initOldBkeClusterFun() {
	initOldBkeCluster = v1beta1.BKECluster{
		ObjectMeta: v12.ObjectMeta{
			Name:      "bkecluster",
			Namespace: "kube-system",
			UID:       "oldxasdfawefraqwerqwer",
			OwnerReferences: []v12.OwnerReference{
				{
					Kind:       "Cluster",
					Name:       initCluster.Name,
					APIVersion: fmt.Sprintf("%s/%s", v1beta12.GroupVersion.Group, v1beta12.GroupVersion.Version),
				},
			},
		},
		Spec: confv1beta1.BKEClusterSpec{
			ClusterConfig: &confv1beta1.BKEConfig{
				Cluster: confv1beta1.Cluster{
					ImageRepo: confv1beta1.Repo{
						Domain: "deploy.bocloud.k8s",
						Port:   "40443",
						Prefix: "kubernetes",
					},
					KubernetesVersion: "v1.24.17",
				},
				Nodes: []confv1beta1.Node{
					{
						Hostname: "cluster",
						Port:     "22",
						Role:     []string{bkenode.MasterWorkerNodeRole, bkenode.EtcdNodeRole},
					},
				},
			},
		},
	}
}
func initClusterFun() {
	initCluster = v1beta12.Cluster{
		ObjectMeta: v12.ObjectMeta{
			Name: "bkecluster", Namespace: "kube-system",
			UID: "xasdfawefraqwerqwer",
		},
		Status: v1beta12.ClusterStatus{
			Phase:      string(v1beta12.ClusterPhaseFailed),
			Conditions: v1beta12.Conditions{{Type: v1beta12.ControlPlaneInitializedCondition, Status: corev1.ConditionTrue}},
		},
	}
}
func InitinitPhaseContextFun() {
	GenClient()

	initLog = &v1beta1.BKELogger{
		Recorder: record.NewBroadcaster().NewRecorder(initScheme,
			corev1.EventSource{Component: "ensure"},
		),
		NormalLogger: testutils.NewLog(),
		EventBinder:  &initNewBkeCluster,
	}

	initPhaseContext = &phaseframe.PhaseContext{
		BKECluster: &initNewBkeCluster,
		Scheme:     initScheme,
		Cluster:    &initCluster,
		Context:    context.Background(),
		Log:        initLog,
	}
	if initClient != nil {
		initPhaseContext.Client = initClient.GetClient()
		initPhaseContext.RestConfig = initClient.GetConfig()
	}

}
func GenClient() {
	initNewBkeClusterFun()
	initOldBkeClusterFun()
	initClusterFun()
	command := initCommand()
	nodes := initNodes()
	kubeConfigSecret := initKubeconfigSecret()
	localKubeconfigSecret := kubeConfigSecret.DeepCopy()
	localKubeconfigSecret.SetName(constant.LocalKubeConfigName)
	localKubeconfigSecret.SetNamespace(v12.NamespaceSystem)
	initClient, initScheme = testutils.TestGetManagerClient([]func(*runtime.Scheme) error{corev1.AddToScheme,
		v1beta1.AddToScheme, v1beta12.AddToScheme, agentv1beta1.AddToScheme},
		&initOldBkeCluster, &initCluster, kubeConfigSecret, nodes, localKubeconfigSecret, command)

}
func initKubeconfigSecret() *corev1.Secret {
	if initTServer != nil {
		initTServer.Close()
	}
	initRestConfig, initTServer = testutils.TestGetK8sServerHttp(nil)
	rconfigBytes, _ := testutils.RestConfigToKubeConfig(initRestConfig, "asdf")
	secret := corev1.Secret{
		ObjectMeta: v12.ObjectMeta{
			Name:      fmt.Sprintf("%s-kubeconfig", initCluster.Name),
			Namespace: initCluster.Namespace,
		},
		Data: map[string][]byte{
			"value": rconfigBytes,
		},
	}
	return &secret
}
func initNodes() *corev1.Node {
	return &corev1.Node{
		ObjectMeta: v12.ObjectMeta{Name: "node1",
			Labels: map[string]string{
				corev1.LabelHostname: "", label.NodeRoleMasterLabel: "",
			},
		},
		Status: corev1.NodeStatus{
			NodeInfo: corev1.NodeSystemInfo{
				KernelVersion: "v1.29.1",
			},
			Addresses: []corev1.NodeAddress{
				{Address: "127.0.0.1"},
			},
			Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}},
		},
	}
}
func initCommand() *agentv1beta1.Command {
	return &agentv1beta1.Command{
		ObjectMeta: v12.ObjectMeta{
			Name:      command.K8sEnvCommandName,
			Namespace: initCluster.Namespace,
			Labels: map[string]string{
				corev1.LabelHostname:      "",
				label.NodeRoleMasterLabel: "",
			},
		},
	}
}
func TestEnsureAddonDeploy(t *testing.T) {
	InitinitPhaseContextFun()
	t.Run("EnsureAddonDeploy", func(t *testing.T) {
		pp := NewEnsureAddonDeploy(initPhaseContext)

		if _, err := pp.Execute(); err != nil {
			t.Log(err)
		}

		deepBkeCluster := initNewBkeCluster.DeepCopy()
		pp.NeedExecute(&initOldBkeCluster, deepBkeCluster)

		if initPhaseContext != nil {
			initPhaseContext.Cluster = nil
		}

		pp = NewEnsureAddonDeploy(initPhaseContext)

		deepBkeCluster.Status.NodesStatus = nil
		pp.NeedExecute(&initOldBkeCluster, deepBkeCluster)

		deepBkeCluster.DeletionTimestamp = &v12.Time{Time: time.Now()}
		pp.NeedExecute(&initOldBkeCluster, deepBkeCluster)

	})
}

func newLocalAndPatchCMs(version string) (*corev1.ConfigMap, *corev1.ConfigMap) {
	bkeCMKey := "patch." + version
	patchCMKey := "cm." + version

	localCM := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "bke-config",
			Namespace: "cluster-system",
		},
		Data: map[string]string{
			bkeCMKey: patchCMKey,
		},
	}

	patchCM := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      patchCMKey,
			Namespace: "openfuyao-patch",
		},
		Data: map[string]string{
			version: "new-patch-content",
		},
	}
	return localCM, patchCM
}

func TestEnsureAddonDeployDistributePatchCMFailGetLocalCM(t *testing.T) {
	InitinitPhaseContextFun()
	if initClient == nil {
		t.Fatal("initClient is nil")
	}
	fakeClient := initClient.GetClient()
	version := "v2.1.0"
	patchCM := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "openfuyao-patch",
			Name:      fmt.Sprintf("cm.%s", version),
		},
		Data: map[string]string{version: "new-patch-content"},
	}
	require.NoError(t, fakeClient.Create(context.Background(), patchCM))

	ppAny := NewEnsureAddonDeploy(initPhaseContext)
	pp, ok := ppAny.(*EnsureAddonDeploy)
	require.True(t, ok, "NewEnsureAddonDeploy should return *EnsureAddonDeploy")
	fakeRemoteClient := fake.NewSimpleClientset()
	pp.mockClient = fakeRemoteClient

	err := pp.distributePatchCM()
	require.Error(t, err)
	assert.Contains(t, err.Error(), "get cm failed")
}

func TestEnsureAddonDeployDistributePatchCMFailGetPatchCM(t *testing.T) {
	InitinitPhaseContextFun()
	version := "v2.1.0"
	localCM := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "cluster-system",
			Name:      "bke-config",
		},
		Data: map[string]string{fmt.Sprintf("patch.%s", version): "some-data"},
	}
	if initClient == nil {
		t.Fatal("initClient is nil")
	}
	fakeClient := initClient.GetClient()
	require.NoError(t, fakeClient.Create(context.Background(), localCM))

	ppAny := NewEnsureAddonDeploy(initPhaseContext)
	pp, ok := ppAny.(*EnsureAddonDeploy)
	require.True(t, ok, "NewEnsureAddonDeploy should return *EnsureAddonDeploy")

	fakeRemoteClient := fake.NewSimpleClientset()
	pp.mockClient = fakeRemoteClient

	err := pp.distributePatchCM()
	require.Error(t, err)
	assert.Contains(t, err.Error(), "get patch cm failed")
}

func TestEnsureAddonDeployDistributePatchCMSSuccessCreate(t *testing.T) {
	InitinitPhaseContextFun()
	version := "v2.1.0"
	localCM, patchCM := newLocalAndPatchCMs(version)
	if initClient == nil {
		t.Fatal("initClient is nil")
	}
	fakeClient := initClient.GetClient()
	require.NoError(t, fakeClient.Create(context.Background(), localCM))
	require.NoError(t, fakeClient.Create(context.Background(), patchCM))

	ppAny := NewEnsureAddonDeploy(initPhaseContext)
	pp, ok := ppAny.(*EnsureAddonDeploy)
	require.True(t, ok, "NewEnsureAddonDeploy should return *EnsureAddonDeploy")

	fakeRemoteClient := fake.NewSimpleClientset()
	pp.mockClient = fakeRemoteClient

	err := pp.distributePatchCM()
	require.NoError(t, err)

	remoteCM, err := fakeRemoteClient.CoreV1().ConfigMaps(constant.OpenFuyaoSystemController).Get(
		context.Background(),
		"patch-config",
		metav1.GetOptions{},
	)
	require.NoError(t, err)
	assert.Equal(t, "new-patch-content", remoteCM.Data["patch-data"])
}
